/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "plugin_model_impl_common.h"


int getEncoderClsid( const WCHAR* format, CLSID* pClsid );
void getGdiplusImageFormat( wchar_t* dest, ImageFormat format );
char* getMimeType( DocumentFormat format );
ImageFormat getImageFormat( DocumentFormat value );
size_t saveToStream( void** pDest, ImageFormat formatDest, void* pSrc, uint_t lengthSrc, ImageFormat formatSrc, ulong_t quality, uint_t width = 0, uint_t height = 0 );
void doUpload( void* arg );
void doSave( void* arg );


#ifndef _ADDON
void _PluginModel::upload()
{
    ::doUpload( this );
}

bool _PluginModel::beginUpload( DocumentFormat format, char* url, List<char*>* filenames, int indices[], int indexCount, AsyncCallback pAsyncCallback, int id )
{
    //m_pPlugin->upload( pAsyncCallback ); // sync mode

    // check if there is any image to upload
    if ( !hasImage() ) return false;

    PluginAsyncInvoke* asyncInvoke = new PluginAsyncInvoke();

    char* mimeType = ::getMimeType( format );
    char* userAgent = m_pPlugin->getUserAgent();
    char* browserUrl = m_pPlugin->getUrl();

    UploadAsyncArg* arg = new UploadAsyncArg( this, url, browserUrl, userAgent, mimeType, format, 0, 0, filenames, indices, indexCount, id ); // if data and length are 0, the document is saved asynchronously

    asyncInvoke->function = ::doUpload;
    asyncInvoke->arg = arg;
    asyncInvoke->handle = getMessageWindow();
    asyncInvoke->callback = pAsyncCallback;

    // begin the upload thread
    m_pUploadThreadHandle = beginInvoke( asyncInvoke );

    return true;
}

void doUpload( void* arg )
{
    UploadAsyncArg& a = *( ( UploadAsyncArg* ) arg );

    URLDESCRIPTOR urlDesc = { 0 };

    ResultCode result = ResultCode::Success;

    if ( Url::Merge( urlDesc, a.getUrl(), a.getNavigatorUrl() ) == 0 )
        result = ResultCode::NetUrl;

    if ( result != ResultCode::Success || urlDesc.protocol == 0 )
    {
        a.setResultCode( result );
        return;
    }

    // create the document if the data is not explicitly specified
    if ( !a.getData() )
    {
        void* data = NULL;
        _PluginModel* model = ( _PluginModel* ) a.getPluginModel();

        ImageList* list = ( model )->getImageList( a.getFormat(), a.getIndices(), a.getIndexCount() );
        if ( list )
        {
            a.setDataList( list );
        }
    }

    //	Sleep( 5000 );

    if ( a.getDataList() || ( a.getData() && a.getDataLength() ) )
    {
        Net net;

        result = net.httpPost( urlDesc.host, urlDesc.port, urlDesc.path, urlDesc.protocol == 2, 
            a.getUserAgent(), 
            a.getDataList(), 
            a.getData(), 
            a.getDataLength(), 
            a.getMimeType(), 
            a.getFilenames() );

        a.setResultCode( result );
    }
}
#endif

void doSave( void* arg )
{
    SaveAsyncArg& a = *( ( SaveAsyncArg* ) arg );
    ResultCode result = ResultCode::Success;

    // TODO: check path

    _PluginModel* model = ( _PluginModel* ) a.getPluginModel();

    void* pdata = NULL;
    size_t size = 0;

    List<wchar_t*>& list = a.getFilenames();
    wchar_t* folder = a.getPath();
    uint_t folderLen = String::GetLength( folder );

    if ( a.getIsMultipage() )
    {	
        size = model->saveToStream( &pdata, a.getFormat(), a.getIndices(), a.getIndexCount() );

        wchar_t* destPath= new wchar_t[ MAX_PATH ];

        ::memset( destPath, 0, MAX_PATH );
        ::memcpy( destPath, folder, folderLen << 1 );

        wchar_t* filename = list[ 0 ];

        if( !::PathAppendW( destPath, filename ) )
        {
            result = ResultCode::SavePathFailed;
            return;
        }

        bool fileExists = false;

        FILE* test = NULL;
        if ( !::_wfopen_s( &test, destPath, L"r" ) )
        {
            fileExists = true;
            ::fclose( test );
            result = ResultCode::SaveFileExists;
        }

        if ( !fileExists )
        {
            FILE* file = NULL;

            errno_t err = ::_wfopen_s( &file, destPath, L"wb" );

            if ( !file )
            {
                result = err == EEXIST ? ResultCode::SaveFileCreateFailed : ResultCode::SaveFileIO;
                ::free( pdata );
                return;
            }

            size_t written = ::fwrite( pdata, 1, size, file );
            ::fclose( file );

            ::free( pdata );
        }
    }
    else
    {
        int* indices = a.getIndices();
        int count = a.getIndexCount();
        int totalImageCount = model->getImageCount();
        count = count == 0 ? totalImageCount : count;

        wchar_t* destPath = new wchar_t[ MAX_PATH ];

        for ( int i = 0; i < count; i++ )
        {
            int index = i + 1;
            if ( indices )
            {
                index = indices[ i ];
                if ( index < 1 || index > totalImageCount )
                {
                    continue;
                }
            }

            ::memset( destPath, 0, MAX_PATH );
            ::memcpy( destPath, folder, folderLen << 1 );

            wchar_t* filename = list[ i ];

            if( !::PathAppendW( destPath, filename ) )
            {
                result = ResultCode::SavePathFailed;
                break;
            }

            size = model->saveToStream( &pdata, a.getFormat(), &index, 1 );
            if ( size == 0 )
            {
                result = ResultCode::SaveStreamWriteFailed;
                break;
            }

            bool fileExists = false;

            FILE* test = NULL;
            if ( !::_wfopen_s( &test, destPath, L"r" ) )
            {
                fileExists = true;
                ::fclose( test );
                result = ResultCode::SaveFileCreateFailed;
            }

            if ( !fileExists )
            {

                FILE* file = NULL;

                errno_t err = ::_wfopen_s( &file, destPath, L"wb" );

                if ( !file )
                {
                    result = ResultCode::SaveFileCreateFailed;
                    continue;
                }

                ::fwrite( pdata, 1, size, file );
                ::fclose( file );

                ::free( pdata );
            }
        }
        delete destPath;
    }

    //	Sleep( 5000 );

    a.setResultCode( result );
}

bool _PluginModel::beginSave( DocumentFormat format, bool multipage, wchar_t* path, List<wchar_t*>* filenames, int indices[], int indexCount, AsyncCallback pAsyncCallback, int id )
{
    // check if there is any image to save
    if ( !hasImage() ) return false;

    PluginAsyncInvoke* asyncInvoke = new PluginAsyncInvoke();

    SaveAsyncArg* arg = new SaveAsyncArg( this, path, format, multipage, filenames, indices, indexCount, id ); // if data and length are 0, the document is saved asynchronously

    asyncInvoke->function = ::doSave;
    asyncInvoke->arg = arg;
    asyncInvoke->handle = getMessageWindow();
    asyncInvoke->callback = pAsyncCallback;

    // begin the save thread
    m_pSaveThreadHandle = ::beginInvoke( asyncInvoke );

    return true;
}

size_t _PluginModel::saveToStream( void** pdata, DocumentFormat format, int indices[], int count )
{
    size_t size = 0;
    *pdata = NULL;

    Image* activeImage = m_activeImage;

    if ( format == DocumentFormat::Pdf )
    {
#ifndef LITE
        uint_t totalImagesCount = m_images.size();
        uint_t imageCount = indices ? count : totalImagesCount;
        uint_t pageCount = imageCount;
        uint_t imageDataLength = 0, imageDataTotal = 0;
        void* imageData = NULL;

        List<PdfImage*> images;

        PdfDocument doc;
        //doc.getPages().addPages( imageCount );

        for ( uint_t i = 0; i < imageCount; i++ )
        {
            uint_t index = i;

            if ( indices )
            {
                index = indices[ i ] - 1;
                if ( index < 0 || index >= totalImagesCount )
                {
                    pageCount--;
                    continue;
                }
            }

            Image* image = m_images[ index ];

            image->lock();
            image->extract();

            const Pixels& p = *image->getPixels();
            byte_t* sourceBits = p.pixels;
            uint_t sourceLength = p.length;

            imageData = NULL;
            PdfEncoding encoding = PdfEncoding::None;

            ImageFilter filter = getFilter( DocumentFormat::Pdf, p.bitsPerPixel );

            switch ( filter )
            {
            case ImageFilter::CCITT:
                {
                    encoding = PdfEncoding::CCITTG4;

                    uint_t destLength = sourceLength;// >> 1;
                    imageData = ( byte_t* ) ::calloc( 1, destLength );
                    //::memset( imageData, 0, destLength );

                    CcittEncoder encoder;

                    imageDataLength = encoder.encodeG4( ( byte_t* ) imageData, sourceBits, p.width, p.height, 8 /*p.rowAlignment*/, 1, p.paletteInverted );
                }
                break;

            case ImageFilter::Flate:
                {
                    encoding = PdfEncoding::Flate;

                    uint_t destLength = sourceLength; //imageProps.bitsPerChannel == 24 ? sourceLength : ( sourceLength >> 1 );
                    imageData = ( byte_t* ) ::calloc( 1, destLength );
                    //::memset( imageData, 0, destLength );

                    byte_t* bits = sourceBits;
                    int size = sourceLength;
                    uint_t sourceStride = p.rowStride;
                    uint_t bytesPerColor = ( p.bitsPerChannel >> 3 );

                    unsigned long destLen = ( unsigned long ) destLength;

                    uint_t tempStride = sourceStride;
                    //unsigned long tempLen = imageProps.bitsPerChannel == 1 ? ( ( imageProps.width * imageProps.height + 7 ) & ( ~7 ) ) >> 3 : tempStride * imageProps.height;
                    unsigned long tempLen = tempStride * p.height;
                    byte_t* tempData = ( byte_t* ) ::malloc( tempLen );

                    // construct the proper pixel data that pdf can understand
                    for ( long line = 0; line < p.height; line++ )
                    {
                        byte_t* tempLine = tempData + line * tempStride;
                        ::memcpy( tempLine, bits + line * sourceStride, tempStride );

                        // fix dib RGB byte order
                        switch ( p.bitsPerPixel )
                        {
                        case 1:

                            if ( !p.paletteInverted )
                            {
                                for ( uint_t j = 0; j < tempStride; j++ )
                                {
                                    tempLine[ j ] = ~tempLine[ j ];
                                }
                            }
                            break;

                        case 24:

                            for ( uint_t j = 0; j < tempStride; j += 3 )
                            {
                                byte_t t = tempLine[ j ];
                                tempLine[ j ] = tempLine[ j + 2 ];
                                tempLine[ j + 2 ] = t;
                            }
                            break;
                        }
                    }

                    if ( !ZlibEncoder::Compress( ( byte_t* ) imageData, &destLen, tempData, tempLen ) )
                        throw "Compression failed!";

                    ::free( tempData );

                    imageDataLength = destLen;
                }
                break;

            case ImageFilter::DCT:
                {
                    encoding = PdfEncoding::DCT;

                    // gdi+
                    //imageDataLength = ::saveToStream( &imageData, ImageFormatJpeg, image->getData(), image->getDataLength(), ImageFormatDib, m_imageQuality );

                    JpegEncoder encoder;

                    imageDataLength = encoder.encode( ( byte_t** ) &imageData, p, m_imageQuality );
                }
                break;
            }

            if ( imageDataLength == 0 )
            {
                throw "Encoder failed!";
            }
            imageDataTotal += imageDataLength;

            if ( image != activeImage && m_autoCompress )
            {
                image->lighten();
            }

            image->unlock();

            doc.getPages().addPages( 1 );

            PdfImage* img = new PdfImage( ( byte* ) imageData, encoding, imageDataLength, p.width, p.height, p.resolution, p.bitsPerChannel, p.bitsPerPixel, !p.paletteInverted );

            images.push_back( img );

            doc.addImage( img );

            doc.getPages().getPage( i ).drawImage( img );
        }

        PdfMemoryStream stream( 1024 + imageDataTotal + ( images.size() << 10 ) );

        doc.save( stream );

        images.destroy();

        return stream.getData( pdata );
#else
        return 0;
#endif
    }
    else
    {
        Image* image = NULL;

        if ( indices )
        {
            if ( indices[ 0 ] > 0 && indices[ 0 ] <= m_images.size() )
            {
                image = m_images[ indices[ 0 ] - 1 ];
            }
        }
        else
        {
            image = activeImage;
        }

        if ( image )
        {
            image->lock();
            image->extract();

            if ( format == DocumentFormat::Jpeg )
            {
                PBITMAPINFOHEADER bih = ( PBITMAPINFOHEADER ) image->getData();

                JpegEncoder encoder;

                size = encoder.encode( ( byte_t** ) pdata, *image->getPixels(), m_imageQuality );
            }
            else if ( format == DocumentFormat::Bmp )
            {
                PBITMAPINFOHEADER bih = ( PBITMAPINFOHEADER ) image->getData();

                BmpEncoder encoder;

                size = encoder.encode( ( byte_t** ) pdata, ( byte_t* ) image->getData(), image->getDataLength() ); //TODO fix for the new internal representation
            }
            else
            {
                byte_t* pSrc = NULL;
                const Pixels& p = *image->getPixels();

                pSrc = ( byte_t* ) this->createDIB( p );

                size = ::saveToStream( pdata, ::getImageFormat( format ), pSrc, image->getDataLength(), ImageFormat::Dib, m_imageQuality );

                ::free( pSrc );
            }

            if ( image != activeImage && m_autoCompress )
            {
                image->lighten();
            }

            image->unlock();
        }
    }
    return size;
}

size_t _PluginModel::saveToFile( DocumentFormat format, bool multipage, int indices[], int count, List<wchar_t*>* filenames )
{
    size_t size = 0;

    Image* activeImage = m_activeImage;

    if ( format == DocumentFormat::Pdf )
    {
        if ( multipage )
        {
            uint_t totalImagesCount = m_images.size();
            uint_t imageCount = indices ? count : totalImagesCount;
            uint_t pageCount = imageCount;
            uint_t imageDataLength = 0, imageDataTotal = 0;
            void* imageData = NULL;

            List<PdfImage*> images;

            PdfDocument doc;

            for ( uint_t i = 0; i < imageCount; i++ )
            {
                uint_t index = i;

                if ( indices )
                {
                    index = indices[ i ] - 1;
                    if ( index < 0 || index >= totalImagesCount )
                    {
                        pageCount--;
                        continue;
                    }
                }

                Image* image = m_images[ index ];

                image->lock();
                image->extract();

                const ImageProps& imageProps = *image->getProperties();
                byte_t* sourceData = ( byte_t* ) image->getData();
                byte_t* sourceBits = ( byte_t* ) Dib::GetBits( ( PBITMAPINFOHEADER ) sourceData );
                uint_t sourceLength = image->getDataLength();

                imageData = NULL;
                PdfEncoding encoding = PdfEncoding::None;

                ImageFilter filter = getFilter( DocumentFormat::Pdf, imageProps.pixelType );

                switch ( filter )
                {
                case ImageFilter::CCITT:
                    {
                        encoding = PdfEncoding::CCITTG4;

                        uint_t destLength = sourceLength;// >> 1;
                        imageData = ( byte_t* ) ::calloc( 1, destLength );
                        //::memset( imageData, 0, destLength );

                        CcittEncoder encoder;

                        imageDataLength = encoder.encodeG4( ( byte_t* ) imageData, sourceBits, imageProps.width, imageProps.height, 32, -1, imageProps.paletteInverted );
                    }
                    break;

                case ImageFilter::Flate:
                    {
                        encoding = PdfEncoding::Flate;

                        uint_t destLength = sourceLength; //imageProps.bitsPerChannel == 24 ? sourceLength : ( sourceLength >> 1 );
                        imageData = ( byte_t* ) ::calloc( 1, destLength );
                        //::memset( imageData, 0, destLength );

                        byte_t* bits = sourceBits;
                        int size = ( sourceLength - ( bits - sourceData ) );
                        uint_t sourceStride = ( ( imageProps.width * imageProps.bitsPerChannel + 31 ) & ( ~31 ) ) >> 3;
                        uint_t bytesPerColor = ( imageProps.bitsPerChannel >> 3 );

                        unsigned long destLen = destLength;

                        uint_t tempStride = ( ( imageProps.width * imageProps.bitsPerChannel + 7 ) & ( ~7 ) ) >> 3; // imageProps.width * byesPerColor
                        //unsigned long tempLen = imageProps.bitsPerChannel == 1 ? ( ( imageProps.width * imageProps.height + 7 ) & ( ~7 ) ) >> 3 : tempStride * imageProps.height;
                        unsigned long tempLen = tempStride * imageProps.height;
                        byte_t* tempData = ( byte_t* ) ::malloc( tempLen );

                        // construct the proper pixel data that pdf can understand
                        for ( uint_t line = 0; line < imageProps.height; line++ )
                        {
                            byte_t* tempLine = tempData + line * tempStride;
                            ::memcpy( tempLine, bits + ( imageProps.height - line - 1 ) * sourceStride, tempStride );

                            // fix dib RGB byte order
                            switch ( imageProps.bitsPerChannel )
                            {
                            case 1:

                                if ( !imageProps.paletteInverted )
                                {
                                    for ( uint_t j = 0; j < tempStride; j++ )
                                    {
                                        tempLine[ j ] = ~tempLine[ j ];
                                    }
                                }
                                break;

                            case 24:

                                for ( uint_t j = 0; j < tempStride; j += 3 )
                                {
                                    byte_t t = tempLine[ j ];
                                    tempLine[ j ] = tempLine[ j + 2 ];
                                    tempLine[ j + 2 ] = t;
                                }
                                break;
                            }
                        }

                        if ( !ZlibEncoder::Compress( ( byte_t* ) imageData, &destLen, tempData, tempLen ) )
                            throw "Compression failed!";

                        ::free( tempData );

                        imageDataLength = destLen;
                    }
                    break;

                case ImageFilter::DCT:
                    {
                        encoding = PdfEncoding::DCT;

                        JpegEncoder encoder;

                        imageDataLength = encoder.encode( ( byte_t** ) &imageData, *image->getPixels(), m_imageQuality );
                    }
                    break;
                }

                if ( imageDataLength == 0 )
                {
                    throw "Encoder failed!";
                }
                imageDataTotal += imageDataLength;

                if ( image != activeImage && m_autoCompress )
                {
                    image->lighten();
                }

                image->unlock();

                doc.getPages().addPages( 1 );

                PdfImage* img = new PdfImage( ( byte* ) imageData, encoding, imageDataLength, imageProps.width, imageProps.height, imageProps.resolution, imageProps.bitsPerChannel, imageProps.bitCount, !imageProps.paletteInverted );

                images.push_back( img );

                doc.addImage( img );

                doc.getPages().getPage( i ).drawImage( img );
            }

            PdfMemoryStream stream( 1024 + imageDataTotal + ( images.size() << 10 ) );

            doc.save( stream );

            images.destroy();

            void* pdata = NULL;

            uint_t dataSize = stream.getData( &pdata );

            FILE* file = NULL;
            errno_t err = ::_wfopen_s( &file, ( *filenames )[ 0 ], L"rw" );
            ::fwrite( pdata, dataSize, 1, file );
            ::fclose( file );
        }
    }
    else
    {
        Image* image = NULL;
        void** pdata = NULL;

        if ( indices )
        {
            if ( indices[ 0 ] > 0 && indices[ 0 ] <= m_images.size() )
            {
                image = m_images[ indices[ 0 ] - 1 ];
            }
        }
        else
        {
            image = activeImage;
        }

        if ( image )
        {
            image->lock();
            image->extract();

            if ( format == DocumentFormat::Jpeg )
            {
                PBITMAPINFOHEADER bih = ( PBITMAPINFOHEADER ) image->getData();

                JpegEncoder encoder;

                size = encoder.encode( ( byte_t** ) pdata, *image->getPixels(), m_imageQuality );
            }
            else if ( format == DocumentFormat::Bmp )
            {
                PBITMAPINFOHEADER bih = ( PBITMAPINFOHEADER ) image->getData();

                BmpEncoder encoder;

                size = encoder.encode( ( byte_t** ) pdata, ( byte_t* ) image->getData(), image->getDataLength() );
            }
            else
            {
                size = ::saveToStream( pdata, ::getImageFormat( format ), image->getData(), image->getDataLength(), ImageFormat::Dib, m_imageQuality );

                FILE* file = NULL;
                errno_t err = ::_wfopen_s( &file, ( *filenames )[ 0 ], L"rw" );
                ::fwrite( pdata, size, 1, file );
                ::fclose( file );
            }

            if ( image != activeImage && m_autoCompress )
            {
                image->lighten();
            }

            image->unlock();
        }
    }
    return size;
}

bool _PluginModel::saveToFile( wchar_t* path, DocumentFormat format, int indices[], int count )
{
    if ( !this->hasImage() ) return false;

    bool result = false;

    void* data = NULL;
    size_t dataLength = this->saveToStream( &data, format, indices, count );

    if ( dataLength )
    {
        FILE *pfile = NULL;
        errno_t result = ::_wfopen_s( &pfile, path, L"wb" );

        if ( ::fwrite( data, sizeof( byte_t ), dataLength, pfile ) == dataLength )
            result = true;

        ::fclose( pfile );
    }

    delete data;

    return result;
}

size_t saveToStream( void** pDest, ImageFormat formatDest, void* pSrc, uint_t lengthSrc, ImageFormat formatSrc, ulong_t quality, uint_t width, uint_t height )
{
    if ( ( !!width ) ^ ( !!height ) ) return 0;

    size_t result = 0;

    ULONG_PTR gdiplusToken = NULL;
    GdiplusStartupInput gdiplusStartupInput;
    ::GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL );

    {
        Bitmap* bitmapSource = NULL;
        IStream* inputStream = NULL;
        if ( formatSrc == ImageFormat::Dib )
        {
            void* pBits = Dib::GetBits( ( PBITMAPINFOHEADER ) pSrc );
            bitmapSource = new Bitmap( ( PBITMAPINFO ) pSrc, pBits );
        }
        else
        {
            if ( ::CreateStreamOnHGlobal( /*&pSrc*/0, TRUE, &inputStream ) == S_OK )
            {
                ULONG written = 0;
                inputStream->Write( pSrc, lengthSrc, &written );
                bitmapSource = new Bitmap( inputStream );
            }
        }

        HBITMAP hbm = NULL;
        bitmapSource->GetHBITMAP( Color::Transparent, &hbm );

        // resize
        if ( width != 0 && height != 0 )
        {
            Bitmap srcBitmap1( hbm, NULL );

            Bitmap destBitmap( width, height, srcBitmap1.GetPixelFormat() );

            Graphics g( &destBitmap );
            g.SetInterpolationMode( InterpolationModeHighQualityBilinear ); //InterpolationModeBilinear );

            HBITMAP hResized = NULL;
            Gdiplus::Status status = g.DrawImage( &srcBitmap1, 0, 0, width, height );

            Status s = destBitmap.GetHBITMAP( Color::Black, &hResized );

            ::DeleteObject( hbm );

            hbm = hResized;
        }
        else
        {
            //bitmapSource->GetHBITMAP( Color::Transparent, &hbm );
        }

        Bitmap bitmapDest( hbm, NULL );

        uint_t dpi = ::lround( ( ( PBITMAPINFOHEADER ) pSrc )->biXPelsPerMeter * 0.0254f );

        Gdiplus::Status st = bitmapDest.SetResolution( ( Gdiplus::REAL ) dpi, ( Gdiplus::REAL ) dpi );
        uint_t colorDepth = ( ( PBITMAPINFOHEADER ) pSrc )->biBitCount;

        CLSID clsid;
        wchar_t imageFormat[ 16 ] = { 0 };
        ::getGdiplusImageFormat( imageFormat, formatDest == ImageFormat::Dib ? ImageFormat::Bmp : formatDest );
        ::getEncoderClsid( imageFormat, &clsid );

        EncoderParameters* pep = NULL;

        if ( formatDest == ImageFormat::Tiff )
        {
            if ( colorDepth == 1 )
            {
                ImageFilter filter = ImageFilter::CCITT; // getFilter( DocumentFormatTiff, PixelTypeBw );

                if ( filter == ImageFilter::CCITT )
                {
                    EncoderParameters* ep = new EncoderParameters[ 1 ];
                    ep->Count = 1;

                    EncoderParameter& eparam = ep->Parameter[ 0 ];
                    eparam.Guid = Gdiplus::EncoderCompression;
                    eparam.NumberOfValues = 1;
                    eparam.Type = Gdiplus::EncoderParameterValueTypeLong;
                    long compression = Gdiplus::EncoderValueCompressionCCITT4;
                    eparam.Value = &compression;

                    pep = ep;
                }
            }
        }
        else if ( formatDest == ImageFormat::Jpeg )
        {
            EncoderParameters* ep = new EncoderParameters[ 2 ];
            ep->Count = 2;

            EncoderParameter& eparam = ep->Parameter[ 0 ];
            eparam.Guid = Gdiplus::EncoderQuality;
            eparam.NumberOfValues = 1;
            eparam.Type = Gdiplus::EncoderParameterValueTypeLong;
            eparam.Value = &quality;

            EncoderParameter& eparam2 = ep->Parameter[ 1 ];
            eparam2.Guid = Gdiplus::EncoderColorDepth;
            eparam2.NumberOfValues = 1;
            eparam2.Type = Gdiplus::EncoderParameterValueTypeLong;
            eparam2.Value = &colorDepth;

            pep = ep;
        }

        //8699894 = 14 + 40 + 1240 * 1754 * 4

        //HGLOBAL hmem = GlobalAlloc( GHND, 5000000 );
        IStream* stream = NULL;
        if ( ::CreateStreamOnHGlobal( NULL/*hmem*/, TRUE, &stream ) == S_OK )
        {
            Gdiplus::Status saveStatus = bitmapDest.Save( stream, &clsid, pep );

            STATSTG stat;
            HRESULT hresult = stream->Stat( &stat, STATFLAG_NONAME );
            size_t size = stat.cbSize.LowPart;
            //stat.cbSize.HighPart = 0;
            HGLOBAL hglobal;
            hresult = ::GetHGlobalFromStream( stream, &hglobal );

            void* buffer = ::GlobalLock( hglobal );

            void*& data = *pDest;
            if ( formatDest == ImageFormat::Dib )
            {
                size -= sizeof( BITMAPFILEHEADER );
                data = new char[ size ];
                ::memcpy( data, ( char* ) buffer + sizeof( BITMAPFILEHEADER ), size );
            }
            else
            {
                data = new char[ size ];
                ::memcpy( data, buffer, size );
            }
            result = size;

            ::GlobalUnlock( hglobal );

            //GlobalFree( hmem );

            stream->Release();
            //CloseHandle( hglobal );
        }

        if ( inputStream )
        {
            inputStream->Release();
        }

        delete bitmapSource;
        if ( pep ) delete pep;

        ::DeleteObject( hbm );
    }

    ::GdiplusShutdown( gdiplusToken );
    return result;
}

void getGdiplusImageFormat( wchar_t* dest, ImageFormat format )
{
    wchar_t* stringFormat = NULL;
    switch ( format )
    {
    case ImageFormat::Default:
    case ImageFormat::Jpeg: stringFormat = L"image/jpeg"; break;
    case ImageFormat::Bmp: stringFormat = L"image/bmp"; break;
    case ImageFormat::Gif: stringFormat = L"image/gif"; break;
    case ImageFormat::Png: stringFormat = L"image/png"; break;
    case ImageFormat::Tiff: stringFormat = L"image/tiff"; break;
    }

    size_t len = String::GetLength( stringFormat );
    ::memcpy( dest, stringFormat, len << 1 );
}

char* getMimeType( DocumentFormat format )
{
    char* stringFormat = NULL;
    switch ( format )
    {
    case DocumentFormat::Default:
    case DocumentFormat::Jpeg: stringFormat = "image/jpeg"; break;
    case DocumentFormat::Bmp: stringFormat = "image/bmp"; break;
    case DocumentFormat::Gif: stringFormat = "image/gif"; break;
    case DocumentFormat::Png: stringFormat = "image/png"; break;
    case DocumentFormat::Tiff: stringFormat = "image/tiff"; break;
    case DocumentFormat::Pdf: stringFormat = "application/pdf"; break;
    }

    char* result = new char[ 32 ];
    ::memset( result, 0, 32 );
    size_t len = String::GetLength( stringFormat );
    ::memcpy( result, stringFormat, len );

    return result;
}

ImageFormat getImageFormat( DocumentFormat value )
{
    switch ( value )
    {
    case DocumentFormat::Jpeg: return ImageFormat::Jpeg;
    case DocumentFormat::Png: return ImageFormat::Png;
    case DocumentFormat::Bmp: return ImageFormat::Bmp;
    case DocumentFormat::Tiff: return ImageFormat::Tiff;
    case DocumentFormat::Gif: return ImageFormat::Gif;
    default:
        throw "Image format not supported!";
    }
}

int getEncoderClsid( const WCHAR* format, CLSID* pClsid )
{
    UINT num = 0;          // number of image encoders
    UINT size = 0;         // size of the image encoder array in bytes

    ImageCodecInfo* pImageCodecInfo = NULL;

    ::GetImageEncodersSize( &num, &size );
    if( size == 0 )
        return -1;  // failure

    pImageCodecInfo = ( ImageCodecInfo* ) ::malloc( size );
    if ( pImageCodecInfo == NULL )
        return -1;  // failure

    ::GetImageEncoders( num, size, pImageCodecInfo );

    for( UINT j = 0; j < num; ++j )
    {
        if( String::Equal( pImageCodecInfo[ j ].MimeType, format ) == 0 )
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            ::free( pImageCodecInfo );
            return j;  // success
        }    
    }

    ::free( pImageCodecInfo );
    return -1;  // failure
}
